using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

using System.Drawing;
using System.Drawing.Drawing2D;
using System.ComponentModel;

using Athena.Core;

namespace Athena.Objects
{
    [Serializable()]
    public abstract class Connector : IDrawable 
    {
        protected float _fThickness;

        public static bool SHOW_LABEL = true;

        public enum ArrowHeadType { Arrow, Bar, Circle, Modulation, Stimulation, Catalysis, Inhibition, Trigger, None  };

        [CategoryAttribute("Connector Settings"), Description("The Thickness of this Curve")]
        public float Thickness
        {
            get { return _fThickness; }
            set { _fThickness = value; }
        }

        //Static methods

        /// <summary>
        /// Returns a new point that is the sum of point1 and point2
        /// </summary>
        /// <param name="point1"></param>
        /// <param name="point2"></param>
        /// <returns></returns>
        public static PointF AddTwoPoints(PointF oPoint1, PointF oPoint2)
        {
            return new PointF(oPoint1.X + oPoint2.X, oPoint1.Y + oPoint2.Y);
        }
        /// <summary>
        /// Adds two points. Each point is scaled by the given amount
        /// </summary>
        /// <param name="oPoint1"></param>
        /// <param name="oPoint2"></param>
        /// <param name="scale1"></param>
        /// <param name="scale2"></param>
        /// <returns></returns>
        public static PointF AddTwoScaledPoints(PointF oPoint1, PointF oPoint2, float scale1, float scale2)
        {
            return new PointF(scale1 * oPoint1.X + scale2 * oPoint2.X, scale1 * oPoint1.Y + scale2 * oPoint2.Y);
        }
        /// <summary>
        /// calculates Euclidean distance between two points
        /// </summary>
        /// <param name="oPoint1"></param>
        /// <param name="oPoint2"></param>
        /// <returns></returns>
        public static float PointDistance(PointF oPoint1, PointF oPoint2)
        {
            try
            {
                return (float)(Math.Sqrt((oPoint1.X - oPoint2.X) * (oPoint1.X - oPoint2.X) + (oPoint1.Y - oPoint2.Y) * (oPoint1.Y - oPoint2.Y)));
            }
            catch (Exception)
            {
                return 0;
            }
        }
        
        /// <summary>
        /// Draws an arrow oriented in the given direction and the tip at the given position
        /// </summary>
        /// <param name="p"></param>
        /// <param name="pen"></param>
        public static void DrawArrow(Graphics g, Pen pen, PointF p0, double angle, float size, ArrowHeadType ArrowHead)
        {
            double delta = (float)Math.PI / 6;
            GraphicsPath oPath = new GraphicsPath();

            switch (ArrowHead)
            {
                case ArrowHeadType.None:
                    break;
                case ArrowHeadType.Trigger:
                    {
                        float fSize = size * 0.7f;

                        p0.X -= fSize / 2f * (float)Math.Cos(angle);
                        p0.Y -= fSize / 2f * (float)Math.Sin(angle);

                        GraphicsPath oTempPath = new GraphicsPath();
                        
                        PointF p1 = new PointF(p0.X + size * (float)Math.Cos(angle - delta), p0.Y + size * (float)Math.Sin(angle - delta)),
                               p2 = new PointF(p0.X + size * (float)Math.Cos(angle + delta), p0.Y + size * (float)Math.Sin(angle + delta)),
                               p3 = new PointF(p0.X + size / 1.5F * (float)Math.Cos(angle - delta), p0.Y + size / 1.5F * (float)Math.Sin(angle - delta)),
                               p4 = new PointF(p0.X + size / 1.5F * (float)Math.Cos(angle + delta), p0.Y + size / 1.5F * (float)Math.Sin(angle + delta));

                        oTempPath.StartFigure();
                        oPath.AddLine(p0, p3);
                        oPath.AddLine(p3, p4);
                        oPath.AddLine(p4, p0);
                        oTempPath.CloseFigure();

                        oTempPath.AddLine(p1,p2);

                        oPath.AddPath(oTempPath, false);
                        
                        g.DrawPath(pen, oPath);
                        break;
                    }
                case ArrowHeadType.Modulation:
                    {
                        size *= 0.7f;

                        delta = (float)Math.PI / 4;

                        p0.X -= size * (float)Math.Sqrt(2) * (float)Math.Cos(angle);
                        p0.Y -= size * (float)Math.Sqrt(2) * (float)Math.Sin(angle);

                        PointF p1 = new PointF(p0.X + size * (float)Math.Cos(angle - delta), p0.Y + size * (float)Math.Sin(angle - delta)),
                               p2 = new PointF(p0.X + size * (float)Math.Cos(angle + delta), p0.Y + size * (float)Math.Sin(angle + delta)),
                               p3 = new PointF(p0.X + size * (float)Math.Sqrt(2) * (float)Math.Cos(angle), p0.Y + size * (float)Math.Sqrt(2) * (float)Math.Sin(angle));

                        oPath.AddLine(p0, p1);
                        oPath.AddLine(p1, p3);
                        oPath.AddLine(p3, p2);
                        oPath.AddLine(p2, p0);

                        //GraphicsPath oTempPath = new GraphicsPath();
                        //oTempPath.AddRectangle(new RectangleF(p0.X, p0.Y, fSize, fSize));
                        //Matrix oMatrix = new Matrix();

                        //oMatrix.Translate(-fSize / 2f, -fSize/2f);
                        //oMatrix.RotateAt(45f, p0, MatrixOrder.Append);
                        
                        //oTempPath.Transform(oMatrix);

                        //oPath.AddPath(oTempPath, false);

                        g.FillPath(Brushes.White, oPath);
                        g.DrawPath(pen, oPath);
                        break;
                    }

                case ArrowHeadType.Arrow:
                    {
                        p0.X -= size / 2 * (float)Math.Cos(angle);
                        p0.Y -= size / 2 * (float)Math.Sin(angle);

                        PointF p1 = new PointF(p0.X + size * (float)Math.Cos(angle - delta), p0.Y + size * (float)Math.Sin(angle - delta)),
                               p2 = new PointF(p0.X + size * (float)Math.Cos(angle + delta), p0.Y + size * (float)Math.Sin(angle + delta)),
                               p3 = new PointF(p0.X + size / 1.5F * (float)Math.Cos(angle), p0.Y + size / 1.5F * (float)Math.Sin(angle));

                        oPath.AddLine(p0, p1);
                        oPath.AddLine(p1, p3);
                        oPath.AddLine(p3, p2);
                        oPath.AddLine(p2, p0);

                        g.FillPath(pen.Brush, oPath);
                    }
                    break;
                case ArrowHeadType.Stimulation:
                    {
                        p0.X -= size / 2 * (float)Math.Cos(angle);
                        p0.Y -= size / 2 * (float)Math.Sin(angle);

                        PointF p1 = new PointF(p0.X + size * (float)Math.Cos(angle - delta), p0.Y + size * (float)Math.Sin(angle - delta)),
                               p2 = new PointF(p0.X + size * (float)Math.Cos(angle + delta), p0.Y + size * (float)Math.Sin(angle + delta)),
                               p3 = new PointF(p0.X + size / 1.5F * (float)Math.Cos(angle), p0.Y + size / 1.5F * (float)Math.Sin(angle));

                        oPath.AddLine(p0, p1);
                        oPath.AddLine(p1, p3);
                        oPath.AddLine(p3, p2);
                        oPath.AddLine(p2, p0);

                        g.DrawPath(pen, oPath);
                    }
                    break;
                case ArrowHeadType.Bar:
                case ArrowHeadType.Inhibition:
                    {
                        p0.X -= pen.Width * (float)Math.Cos(angle);
                        p0.Y -= pen.Width * (float)Math.Sin(angle);

                        delta = (float)Math.PI / 2;
                        p0.X += 1.5F * (float)Math.Cos(angle);
                        p0.Y += 1.5F * (float)Math.Sin(angle);
                        PointF p1 = new PointF(p0.X + size * (float)Math.Cos(angle - delta), p0.Y + size * (float)Math.Sin(angle - delta)),
                               p2 = new PointF(p0.X + size * (float)Math.Cos(angle + delta), p0.Y + size * (float)Math.Sin(angle + delta));

                        g.DrawLine(pen, p1, p2);
                    }
                    break;
                case ArrowHeadType.Circle:
                case ArrowHeadType.Catalysis:
                    {
                        /*PointF p1 = new PointF(p0.X + size * (float)Math.Cos(angle - delta), p0.Y + size * (float)Math.Sin(angle - delta)),
                               p2 = new PointF(p0.X + size * (float)Math.Cos(angle + delta), p0.Y + size * (float)Math.Sin(angle + delta)),
                               p3 = new PointF(p0.X + size / 1.5F * (float)Math.Cos(angle), p0.Y + size / 1.5F * (float)Math.Sin(angle)),
                               p4 = new PointF(p3.X + size * (float)Math.Cos(angle - delta), p3.Y + size * (float)Math.Sin(angle - delta)),
                               p5 = new PointF(p3.X + size * (float)Math.Cos(angle + delta), p3.Y + size * (float)Math.Sin(angle + delta));

                        g.DrawLine(pen, p0, p1);
                        g.DrawLine(pen, p0, p2);
                        g.DrawLine(pen, p3, p4);
                        g.DrawLine(pen, p3, p5);*/

                        g.FillEllipse(new SolidBrush(Program.Model.Background.StartColor), p0.X - size / 2, p0.Y - size / 2, size, size);
                        g.DrawEllipse(pen, p0.X - size / 2, p0.Y - size / 2, size, size);
                    }
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// Draws an arrow oriented in the given direction and the tip at the given position
        /// </summary>
        /// <param name="p"></param>
        /// <param name="pen"></param>
        public static void ArrowStyle(Graphics g, Pen pen, PointF p0, double angle, float size, ArrowHeadType ArrowHead)
        {
            double delta = (float)Math.PI / 6;
            GraphicsPath oPath = new GraphicsPath();

            switch (ArrowHead)
            {
                case ArrowHeadType.Arrow:                    
                    {
                        p0.X -= size / 2 * (float)Math.Cos(angle);
                        p0.Y -= size / 2 * (float)Math.Sin(angle);

                        PointF p1 = new PointF(p0.X + size * (float)Math.Cos(angle - delta), p0.Y + size * (float)Math.Sin(angle - delta)),
                               p2 = new PointF(p0.X + size * (float)Math.Cos(angle + delta), p0.Y + size * (float)Math.Sin(angle + delta)),
                               p3 = new PointF(p0.X + size / 1.5F * (float)Math.Cos(angle), p0.Y + size / 1.5F * (float)Math.Sin(angle));

                        oPath.AddLine(p0, p1);
                        oPath.AddLine(p1, p3);
                        oPath.AddLine(p3, p2);
                        oPath.AddLine(p2, p0);

                        g.FillPath(pen.Brush, oPath);
                    }
                    break;
                case ArrowHeadType.Bar:
                    {
                        p0.X -= pen.Width * (float)Math.Cos(angle);
                        p0.Y -= pen.Width * (float)Math.Sin(angle);

                        delta = (float)Math.PI / 2;
                        p0.X += 1.5F * (float)Math.Cos(angle);
                        p0.Y += 1.5F * (float)Math.Sin(angle);
                        PointF p1 = new PointF(p0.X + size * (float)Math.Cos(angle - delta), p0.Y + size * (float)Math.Sin(angle - delta)),
                               p2 = new PointF(p0.X + size * (float)Math.Cos(angle + delta), p0.Y + size * (float)Math.Sin(angle + delta));

                        g.DrawLine(pen, p1, p2);
                    }
                    break;
                case ArrowHeadType.Circle:
                case ArrowHeadType.Catalysis:
                    {
                        /*PointF p1 = new PointF(p0.X + size * (float)Math.Cos(angle - delta), p0.Y + size * (float)Math.Sin(angle - delta)),
                               p2 = new PointF(p0.X + size * (float)Math.Cos(angle + delta), p0.Y + size * (float)Math.Sin(angle + delta)),
                               p3 = new PointF(p0.X + size / 1.5F * (float)Math.Cos(angle), p0.Y + size / 1.5F * (float)Math.Sin(angle)),
                               p4 = new PointF(p3.X + size * (float)Math.Cos(angle - delta), p3.Y + size * (float)Math.Sin(angle - delta)),
                               p5 = new PointF(p3.X + size * (float)Math.Cos(angle + delta), p3.Y + size * (float)Math.Sin(angle + delta));

                        g.DrawLine(pen, p0, p1);
                        g.DrawLine(pen, p0, p2);
                        g.DrawLine(pen, p3, p4);
                        g.DrawLine(pen, p3, p5);*/

                        g.FillEllipse(new SolidBrush(Program.Model.Background.StartColor), p0.X - size / 2, p0.Y - size / 2, size, size);
                        g.DrawEllipse(pen, p0.X - size / 2, p0.Y - size / 2, size, size);
                    }
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// Class Control Point instanciates a PointF with additional features like adding, scaling, etc
        /// This class is mainly constructed to make PointF into a user time (for pointing)
        /// </summary>
        [Serializable()]
        public class ControlPoint   //converts value type point to user type so that it can be pointed to
        {
            PointF point;
            public ControlPoint() { }
            public ControlPoint(float x, float y) { point = new PointF(x, y); }
            public ControlPoint(ControlPoint copy) { point = copy.point; }
            public ControlPoint(PointF _point) { point = _point; }
            public ControlPoint Add(ControlPoint _bpoint2)
            {
                point.X += _bpoint2.X; point.Y += _bpoint2.Y;
                return this;
            }
            public ControlPoint AddScaled(ControlPoint _bpoint2, float factor1, float factor2)
            {
                point.X = factor1 * point.X + factor2 * _bpoint2.X;
                point.Y = factor1 * point.Y + factor2 * _bpoint2.Y;
                return this;
            }
            public ControlPoint Add(PointF _bpoint2)
            {
                point.X += _bpoint2.X; point.Y += _bpoint2.Y;
                return this;
            }
            public ControlPoint AddScaled(PointF _bpoint2, float factor1, float factor2)
            {
                point.X = factor1 * point.X + factor2 * _bpoint2.X;
                point.Y = factor1 * point.Y + factor2 * _bpoint2.Y;
                return this;
            }
            public ControlPoint Scale(float factor)
            {
                point.X *= factor;
                point.Y *= factor;
                return this;
            }

            public float X
            {
                get { return point.X; }
                set { point.X = value; }
            }
            public float Y
            {
                get { return point.Y; }
                set { point.Y = value; }
            }
            public PointF Point
            {
                get { return point; }
                set { point = value; }
            }
            public override string ToString()
            {
                return point.ToString();
            }
        }

        /// <summary>
        /// Redo the control points to make the whole thing clean again
        /// </summary>
        public virtual void MakePretty() { }

        /// <summary>
        /// This method is used to inform the connector that a node has been removed. Returns false if this
        /// removal will remove the only input or output from the connector 
        /// </summary>
        /// <param name="ig"></param>
        public virtual bool RemoveNode(IDrawable ig) { return false; }
        
        /// <summary>
        /// Returns a list of control points that lie in this rect
        /// </summary>
        /// <param name="oRect"></param>
        /// <returns></returns>
        public virtual List<ControlPoint> SelectControlPointsAt(RectangleF oRect) { return null; }

        /// <summary>
        /// Selects all control points. To be used in conjunction with SelectedControlPoints
        /// </summary>
        /// <param name="oRect"></param>
        /// <returns></returns>
        public virtual void SelectAllControlPoints() { }


        /// <summary>
        /// returns a list of currently selected control points (usually a result of Where)
        /// </summary>
        public virtual List<ControlPoint> SelectedControlPoints
        {
            get { return null; }
        }

        /// <summary>
        /// returns the topmost control point near this point
        /// </summary>
        /// <param name="oPoint"></param>
        /// <returns></returns>
        public virtual ControlPoint SelectControlPointsAt(PointF oPoint) { return null; }
        /// <summary>
        /// Moves all the control points given. To be used in conjunction with SelectControlPoints
        /// </summary>
        /// <param name="SelectedControlPoints"></param>
        /// <param name="oDirection"></param>
        public virtual void MoveSelectedControlPoints(List<ControlPoint> SelectedControlPoints, PointF oDirection) { }
        /// <summary>
        /// Inserts a new input item. Inserts this item into the segments list at the front
        /// </summary>
        /// <param name="item"></param>
        public virtual void AddReactant(IDrawable item) { }

        /// <summary>
        /// inserts a new item into the segments list at the end
        /// </summary>
        /// <param name="item"></param>
        public virtual void AddProduct(IDrawable item) { }

        /// <summary>
        /// inserts a new item into the segments list at the beginning
        /// </summary>
        /// <param name="item"></param>
        public virtual void AddModifier(IDrawable item) { }
        
        /// <summary>
        /// Gets / Sets the list of input Idrawables connected to this conenctor
        /// </summary>
        [CategoryAttribute("Inputs and Outputs"), Description("The inputs for this reaction")]
        public virtual List<IDrawable> Reactants
        {
            get { return null; }
            set { }
        }
        
        /// <summary>
        /// Gets / Sets the list of Idrawables that are connected as outputs
        /// </summary>
        /// <returns></returns>
        [CategoryAttribute("Inputs and Outputs"), Description("The outputs for this reaction")]
        public virtual List<IDrawable> Products
        {
            get { return null; }
            set { }
        }
        /// <summary>
        /// Gets / Sets the list of stoichiometries for the input reactants
        /// </summary>
        public virtual double[] ReactantStoichiometry 
        {
            get
            {
                if (Item is Edge)
                {
                    return ((Edge)Item).ReactantsStoichiometry;
                }
                else
                    return null;
            }
        }
        /// <summary>
        /// Sets the stoichiometry of the given node
        /// </summary>
        /// <param name="id"></param>
        /// <param name="value"></param>
        public virtual void SetReactantStoichiometry(string id, double value, bool add)
        {
            if (Item is Edge)
            {
                Edge e = (Edge)Item;
                if (add)
                {
                    double d = e.GetReactantStoichiometry(id);
                    e.SetReactantStoichiometry(id, d + value);
                }
                else
                {
                    e.SetReactantStoichiometry(id, value);
                }
            }
        }
        /// <summary>
        /// Sets the stoichiometry of the given node
        /// </summary>
        /// <param name="id"></param>
        /// <param name="value"></param>
        public virtual void SetReactantStoichiometry(IDrawable id, double value, bool add)
        {
            if (Item is Edge && id != null && id.Item is Node)
            {
                Edge e = (Edge)Item;
                if (add)
                {
                    double d = e.GetReactantStoichiometry((Node)id.Item);
                    e.SetReactantStoichiometry((Node)id.Item, d + value);
                }
                else
                {
                    e.SetReactantStoichiometry((Node)id.Item, value);
                }
            }
        }
        /// <summary>
        /// Gets the stoichiometry of the given node
        /// </summary>
        /// <param name="id"></param>
        /// <param name="value"></param>
        public virtual double GetReactantStoichiometry(string id)
        {
            if (Item is Edge)
            {
                return ((Edge)Item).GetReactantStoichiometry(id);
            }
            return 0;
        }
        /// <summary>
        /// Sets the stoichiometry of the given node
        /// </summary>
        /// <param name="id"></param>
        /// <param name="value"></param>
        public virtual double GetReactantStoichiometry(IDrawable id)
        {
            if (Item is Edge && id != null && id.Item is Node)
            {
                return ((Edge)Item).GetReactantStoichiometry((Node)id.Item);
            }
            return 0.0d;
        }
        /// <summary>
        /// Sets the stoichiometry of the given node
        /// </summary>
        /// <param name="id"></param>
        /// <param name="value"></param>
        public virtual void SetProductStoichiometry(string id, double value, bool add)
        {
            if (Item is Edge)
            {
                Edge e = (Edge)Item;
                if (add)
                {
                    double d = e.GetProductStoichiometry(id);
                    e.SetProductStoichiometry(id, d + value);
                }
                else
                {
                    e.SetProductStoichiometry(id, value);
                }
            }
        }
        /// <summary>
        /// Sets the stoichiometry of the given node
        /// </summary>
        /// <param name="id"></param>
        /// <param name="value"></param>
        public virtual void SetProductStoichiometry(IDrawable id, double value, bool add)
        {
            if (Item is Edge && id != null && id.Item is Node)
            {
                Edge e = (Edge)Item;
                if (add)
                {
                    double d = e.GetProductStoichiometry((Node)id.Item);
                    e.SetProductStoichiometry((Node)id.Item, d + value);
                }
                else
                {
                    e.SetProductStoichiometry((Node)id.Item, value);
                }
            }
        }
        /// <summary>
        /// Gets the stoichiometry of the given node
        /// </summary>
        /// <param name="id"></param>
        /// <param name="value"></param>
        public virtual double GetProductStoichiometry(string id)
        {
            if (Item is Edge)
            {
                return ((Edge)Item).GetProductStoichiometry(id);
            }
            return 0;
        }
        /// <summary>
        /// Sets the stoichiometry of the given node
        /// </summary>
        /// <param name="id"></param>
        /// <param name="value"></param>
        public virtual double GetProductStoichiometry(IDrawable id)
        {
            if (Item is Edge && id != null && id.Item is Node)
            {
                return ((Edge)Item).GetProductStoichiometry((Node)id.Item);
            }
            return 0.0d;
        }
        /// <summary>
        /// Gets / Sets the list of stoichiometries for the input reactants
        /// </summary>
        public virtual double[] ProductStoichiometry
        {
            get
            {
                if (Item is Edge)
                {
                    return ((Edge)Item).ProductsStoichiometry;
                }
                else
                    return null;
            }
        }
        /// <summary>
        /// the number of inputs
        /// </summary>
        [CategoryAttribute("Inputs and Outputs"), Description("The number of inputs in this reaction")]
        public virtual int ReactantCount
        {
            get { return Reactants.Count; }
        }
        /// <summary>
        /// number of outputs
        /// </summary>
        [CategoryAttribute("Inputs and Outputs"), Description("The number of outputs in this reaction")]
        public virtual int ProductCount
        {
            get { return Products.Count; }
        }

        /// <summary>
        /// number of modifiers
        /// </summary>
        [CategoryAttribute("Inputs and Outputs"), Description("The number of outputs in this reaction")]
        public virtual int ModifierCount
        {
            get { return Modifiers.Count; }
        }

        /// <summary>
        /// Returns a list of Idrawables that are connected as input and outputs
        /// </summary>
        /// <returns></returns>
        [CategoryAttribute("Inputs and Outputs"), Description("All items that partake in this reaction")]
        public abstract List<IDrawable> Nodes
        {
            get;
        }
        /*
        /// <summary>
        /// Gets / Sets the parameter initializations (semi-colon separated eg.: k1=2;k3=0.5;)
        /// </summary>
        [CategoryAttribute("Rate Law"), Description("The parameter initializations")]
        public virtual string ParameterInitializations
        {
            get 
            {
                if (Item != null && Item is Edge)
                    return ((Edge)Item).GetParameterInitializations(",");
                else
                    return "";
            }
            set 
            {
                if (Item != null && Item is Edge)
                    ((Edge)Item).SetParameterInitializations(value,','); 
            }
        }
        */
        [CategoryAttribute("Rate Law"), Description("The reaction rate")]
        public virtual string Rate
        {
            get
            {
                if (Item != null && Item is Edge)
                    return ((Edge)Item).Rate;
                else
                    return "0";
            }
            set
            {
                if (Item != null && Item is Edge && !FixedReaction)
                    ((Edge)Item).Rate = value;  
            }
        }

        /*
        public double this[string s]
        {
            get
            {
                if (Item is Edge)
                {
                    return ((Edge)Item).GetParameter(s);
                }
                else
                    return double.NaN;
            }
            set
            {
                if (Item is Edge)
                {
                    ((Edge)Item).SetParameter(s,value);
                }
            }
        }*/

        /// <summary>
        /// make the item
        /// </summary>
        /// <param name="list">all Idrawables to connect -- inputs first, outputs last</param>
        /// <param name="inputs">number of inputs (starting from index 0)</param>
        protected virtual void MakeItem(List<IDrawable> list, int inputs, bool makeItem)
        {
            List<Node> nodes = new List<Node>();
            List<Edge> edges = new List<Edge>();

            int i = 0;

            foreach (IDrawable id in list)    //all the nodes that these idrawables belong with
            {
                if (id != null && id.Item != null)
                {
                    if (id.Item is Node)// && (i < inputs || id.Item is Species))
                    {
                        nodes.Add(id.Item as Node);
                    }
                    else
                    {
                        if (id is Connector)
                        {
                            ((Connector)id).Modifiers.Add(this);
                            edges.Add(id.Item as Edge);
                            //_Item = id.Item;
                            makeItem = false;
                        }
                        else
                        {
                            if (id.Item is Edge)
                            {
                                edges.Add(id.Item as Edge);
                                //_Item = id.Item;
                                makeItem = false;
                            }
                            /*else
                                if (id.Item is Part)
                                {
                                    Edge pops = ((Part)id.Item).PoPS;
                                    if (pops != null)
                                        edges.Add(pops as Edge);
                                    //_Item = pops;
                                    //makeItem = false;
                                }*/
                        }
                    }
                }
                ++i;
            }


            if (_Item == null && makeItem)
                if (nodes.Count > inputs || edges.Count < 1)
                {
                    Edge edge = new Edge(nodes, inputs);  //make the edge 
                    Item = edge;
                }
            
            foreach (Edge e in edges)    //let the output edges know that
            {
                int k=0;
                foreach (Node n in nodes)  //they are being modified
                {
                    if (k < inputs)
                        e.AddModifier(n);
                    ++i;
                }
            }

            _fThickness = 2;
        }
        /// <summary>
        /// make the item
        /// </summary>
        /// <param name="list">list of inputs</param>
        /// <param name="inputs">list of outputs</param>
        protected virtual void MakeItem(List<IDrawable> inputList, List<IDrawable> outputList, bool makeItem)
        {
            List<Node> inputs = new List<Node>(), outputs = new List<Node>();
            List<Edge> edges = new List<Edge>();
            foreach (IDrawable id in inputList)    //all the nodes that these idrawables belong with
            {
                if (id != null && id.Item != null)
                {
                    if (id.Item is Node)
                        inputs.Add(id.Item as Node);
                    else
                    {
                        if (id is Connector)
                        {
                            ((Connector)id).Modifiers.Add(this);
                            edges.Add(id.Item as Edge);
                            //_Item = id.Item;
                            makeItem = false;
                        }
                        else
                        {
                            if (id.Item is Edge)
                            {
                                edges.Add(id.Item as Edge);
                                //_Item = id.Item;
                                makeItem = false;
                            }
                            /*else
                                if (id.Item is Part)
                                {
                                    Edge pops = ((Part)id.Item).PoPS;
                                    if (pops != null)
                                        edges.Add(pops as Edge);
                                    //_Item = pops;
                                    //makeItem = false;
                                }*/
                        }
                    }
                }
            }
            foreach (IDrawable id in outputList)    //all the nodes that these idrawables belong with
            {
                if (id != null && id.Item != null)
                {
                    if (id.Item is Node)
                        outputs.Add(id.Item as Node);
                    else
                    {
                        if (id is Connector)
                        {
                            ((Connector)id).Modifiers.Add(this);
                            edges.Add(id.Item as Edge);
                            //_Item = id.Item;
                            makeItem = false;
                        }
                        else
                        {
                            if (id.Item is Edge)
                            {
                                edges.Add(id.Item as Edge);
                                //_Item = id.Item;
                                makeItem = false;
                            }
                            /*else
                                if (id.Item is Part)
                                {
                                    Edge pops = ((Part)id.Item).PoPS;
                                    if (pops != null)
                                        edges.Add(pops as Edge);
                                    //_Item = pops;
                                    //makeItem = false;
                                }*/
                        }
                    }
                }
            } 

            foreach (Edge e in edges)    //let the output edges know that
            {
                foreach (Node n in inputs)  //they are being modified
                {
                    e.AddModifier(n);             
                }
            }

            if (_Item == null && makeItem)
                if (outputs.Count > 0 || edges.Count < 1)
                {
                    Edge edge = new Edge(inputs, outputs);  //make the edge
                    Item = edge;
                }

            _fThickness = 2;
        }

        /// <summary>
        /// Makes the corresponding Item 
        /// </summary>
        /// <param name="list">list of inputs</param>
        /// <param name="inputs">list of outputs</param>
        protected virtual void MakeItem(IDrawable input, IDrawable output, bool makeItem)
        {
            List<Node> inputs = new List<Node>(), outputs = new List<Node>();
            List<Edge> edges = new List<Edge>();

            if (input != null && input.Item != null)
            {
                if (input.Item is Node)
                    inputs.Add(input.Item as Node);
                else
                    if (input is Connector)
                    {
                        ((Connector)input).Modifiers.Add(this);
                        edges.Add(input.Item as Edge);
                    }
            }

            if (output != null && output.Item != null)
            {
                if (output.Item is Node)
                    outputs.Add(output.Item as Node);
                else
                {
                    if (output is Connector)
                    {
                        ((Connector)output).Modifiers.Add(this);
                        edges.Add(output.Item as Edge);
                        //_Item = output.Item;
                        makeItem = false;
                    }
                    else
                    {
                        if (output.Item is Edge)
                        {
                            edges.Add(output.Item as Edge);
                            //_Item = output.Item;
                            makeItem = false;
                        }
                        /*else
                            if (output.Item is Part)
                            {
                                Edge pops = ((Part)output.Item).PoPS;
                                if (pops != null)
                                    edges.Add(pops as Edge);
                                //_Item = pops;
                                //makeItem = false;
                            }*/
                    }
                }
            }

            foreach (Edge e in edges)    //let the output edges know that
            {
                foreach (Node n in inputs)  //they are being modified
                {
                    e.AddModifier(n);
                }
            }

            if (_Item == null && makeItem)
            {
                Edge edge = new Edge(inputs, outputs);  //make the edge
                Item = edge;
            }

            _fThickness = 2;
        }

        /// <summary>
        /// get a list of all the modifers -- these are usually other connectors
        /// </summary>
        public virtual List<IDrawable> Modifiers
        {
            get { return new List<IDrawable>(); }
            set {                }
        }
        /// <summary>
        /// is this reaction a regulatory reaction
        /// </summary>
        public virtual bool IsRegulation
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Get the middle point
        /// </summary>
        [CategoryAttribute("Middle Region"), Description("The middle region of this connection")]
        public virtual RectangleF MiddleRegion { get { return Bounds; } }

        /// <summary>
        /// A volatile list that keeps track of which points were temporarily selected for moving/modification
        /// </summary>
        protected List<ControlPoint> _SelectedControlPoints;

        /// <summary>
        /// replaces all instances of one node with another in this reaction
        /// </summary>
        /// <param name="NodeToReplace"></param>
        /// <param name="ReplaceWith"></param>
        public virtual void SubstituteNode(IDrawable NodeToReplace, IDrawable ReplaceWith) { }

        [CategoryAttribute("Rate Law"), Description("The reaction")]
        public virtual string Reaction
        {
            get
            {
                if (Item != null && Item is Edge)
                {
                    return (Item as Edge).ToString();
                }
                else
                    return "NA";
            }
        }

        /// <summary>
        /// Returns the arrow head type for the selected segment (first one if multiple selected)
        /// Sets the arrow head type for all selected segments
        /// </summary>
        [CategoryAttribute("Arrow Head"), Description("The type of arrow head of this curve")]
        public virtual Connector.ArrowHeadType ArrowHead
        {
            get
            {
                return ArrowHeadType.Arrow;
            }
            set
            {
            }
        }

        /// <summary>
        /// adjusts the parent IF all the nodes have the same parent
        /// </summary>
        public virtual void SyncParent()
        {
            List<IDrawable> nodes = new List<IDrawable>();
            
            nodes.AddRange(Reactants);
            nodes.AddRange(Products);

            if (nodes.Count < 1) return;

            IDrawable commonParent = nodes[0].Parent;

            if (commonParent != null)
            {
                foreach (IDrawable item in nodes)
                {
                    //get the eldest parent
                    if (item.Parent != null && item.Parent.IsParentOf(commonParent))
                    {
                        commonParent = item.Parent;
                    }

                    //make sure the eldest parent is parent to all the nodes
                    if (!commonParent.IsParentOf(item))
                    {
                        commonParent = null;  //no?
                        break;
                    }
                }
            }
            if (commonParent != null)  //if there is a common ancestor to all connected nodes
            {
                Parent = commonParent;       //then that is also this connector's parent
            }
        }

        /// <summary>
        /// Replace each node with an alias
        /// </summary>
        public virtual void ReplaceNodesWithNearestAlias()
        {
        }

        protected bool _FixedReaction = false;

        /// <summary>
        /// Determined whether the input/output of this connector can be changed
        /// </summary>
        public bool FixedReaction
        {
            get { return _FixedReaction; }
            set { _FixedReaction = value; }
        }

        /// <summary>
        /// Checks the rate expression and automatically makes all the modifier connectors
        /// </summary>
        public virtual void GenerateModifiers() { }

        /// <summary>
        /// Writes the connectors attributes to xml
        /// </summary>
        /// <param name="writer"></param>
        public override void WriteXML(System.Xml.XmlWriter writer)
        {
            base.WriteXML(writer);
            writer.WriteStartElement("Connector", "");

            writer.WriteStartElement("FixedReaction", "");
            writer.WriteValue(_FixedReaction);
            writer.WriteEndElement();

            writer.WriteStartElement("Thickness", "");
            writer.WriteValue(_fThickness);
            writer.WriteEndElement();

            writer.WriteEndElement();
        }

        public override bool ReadXML(System.Xml.XmlReader reader)
        {
            bool read = base.ReadXML(reader);
            
            read = reader.Read();

            if (reader.NodeType == XmlNodeType.Element && reader.Name == "Connector")
            {
                read = reader.Read();
                while (read)
                {
                    if (reader.NodeType == XmlNodeType.EndElement)
                        break;

                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        switch (reader.Name)
                        {
                            case "FixedReaction":
                                read = reader.Read();
                                if (reader.NodeType == XmlNodeType.Text)
                                    _FixedReaction = reader.ReadContentAsBoolean();
                                
                                break;
                            case "Thickness":
                                read = reader.Read();
                                if (reader.NodeType == XmlNodeType.Text)
                                    _fThickness = reader.ReadContentAsFloat();
                                
                                break;
                            default:
                                while (reader.NodeType != XmlNodeType.EndElement)
                                    read = reader.Read();
                                break;
                        }
                    }
                    if (!read || (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Connector")) break;

                    if (reader.NodeType != XmlNodeType.Element)
                        read = reader.Read();
                }
                return true;
            }
            else
                return false;
        }
    }
}
